package com.smsc.headend.task.engine.executor.impl.ansi;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode;
import com.smsc.headend.common.utils.RedisKeys;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.data.dmo.DeviceSAPandKeys;
import com.smsc.headend.module.data.dto.AnsiSubDataItemDTO;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.enums.DictionaryType;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.report.entity.LogScheduleTaskDevice;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.module.task.dto.BlockInfo;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.module.task.enums.TaskType;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.executor.TaskExecutor;
import com.smsc.headend.task.engine.facade.DataItemFacade;
import com.smsc.headend.task.engine.handler.impl.ansi.ReadAnsiProfileDataHandler;
import com.smsc.headend.task.engine.service.ConnectorService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.task.LogScheduleTaskDeviceService;
import com.smsc.headend.task.engine.service.task.TaskPacketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.util.CollectionUtils;

import java.util.*;

@Slf4j
public class ReadAnsiProfileDataExecutor implements TaskExecutor {
    @Autowired
    TaskPacketService packetService;
    @Autowired
    ConnectorService connectorService;

    @Autowired
    RedisUtils redisUtils;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;
    @Autowired
    DataItemFacade dataItemFacade;

    @Autowired
    KafkaTemplate kafkaTemplate;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    AssetService assetService;
    @Autowired
    LogScheduleTaskDeviceService logScheduleTaskDeviceService;
    private static final int MAX_READ_SIZE = 88;

    @Override
    public AtomicTaskState execute(Task task, AtomicTask atomicTask) throws UDISTaskExecutorException {
        Meter meter = getMeter(task, assetService);
        DeviceOperation dictionary = assetService.getDictionary(atomicTask.getOperationId(), DictionaryType.Operation.getType());
        ComConfigDMO comConfigDMO = assetService.getComConfigById(task.getComId());
        List<DataItemDTO> dataItemCosemList = dataItemFacade.getDataItemsByOperation(meter, null, dictionary, getParameterMap(task, atomicTask), comConfigDMO == null ? null : comConfigDMO.getModelId());
        if (CollectionUtils.isEmpty(dataItemCosemList)) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.DATAITEM_NOT_EXISTS.toString());
        }
        AnsiSubDataItemDTO dataItemDTO = (AnsiSubDataItemDTO) dataItemCosemList.get(0);
        DeviceSAPandKeys deviceKeys = assetManagementRemoteService.getMeterCommunicationKey(meter.getMeterId(), dataItemDTO.getRoleId());
        AtomicTaskState atomicState = taskDAO.hashGetAtomicTaskState(atomicTask.getAtomicTaskNo());
        String ansiTaskDataKey = RedisKeys.getAnsiTaskDataKey(task.getTaskNo());
        Map ansiTaskData = redisUtils.hmget(ansiTaskDataKey);
        int nbrChnsSet1 = MapUtil.getInt(ansiTaskData, "NBR_CHNS_SET1");
        int nbrBlkIntsSet1 = MapUtil.getInt(ansiTaskData, "NBR_BLK_INTS_SET1");
        long startIntervalTime;
        int currentIntervalOffset;
        int maxIntTimeSet1 = MapUtil.getInt(ansiTaskData, "MAX_INT_TIME_SET1");
        int interval = maxIntTimeSet1 * 60;
        int blockNum;
        int pointNum;
        if(atomicState == null) {
            atomicState = createAtomicState(ReadAnsiProfileDataHandler.class);
            LinkedList<BlockInfo> matchedBlockNums = MapUtil.get(ansiTaskData, "MATCHED_BLOCK_NUMS", LinkedList.class);
            atomicState.setMatchedBlockNumbers(matchedBlockNums);
            atomicState.setData(ansiTaskData);
            // calculate offset
            Long profileStartTime = MapUtil.getLong(ansiTaskData, "profileStartTime");
            BlockInfo firstBlock = matchedBlockNums.poll();
            blockNum = firstBlock.getBlockNum();
            long startTime = firstBlock.getFirstTime();
            if(startTime % interval != 0) {
                startTime += (interval - startTime % interval);
            }

            if(profileStartTime % interval != 0) {
                profileStartTime += (interval - startTime % interval);
            }
            log.info("first block start time {}", DateUtil.date(startTime * 1000));
            if(profileStartTime - startTime < 0) {
                pointNum = 0;
            } else {
                pointNum = (int) (profileStartTime - startTime) / interval;
            }
            int offset = pointNum * 4 * nbrChnsSet1;
            int startOffset = firstBlock.getOffset();
            startIntervalTime = startTime + pointNum * interval;
            currentIntervalOffset = startOffset + offset;
        } else {
            currentIntervalOffset = MapUtil.getInt(atomicState.getData(), "CURRENT_INTERVAL_OFFSET");
            startIntervalTime = MapUtil.getLong(atomicState.getData(), "START_INTERVAL_TIME");
            blockNum = MapUtil.getInt(atomicState.getData(), "CURRENT_BLOCK_NUM");
            pointNum = MapUtil.getInt(atomicState.getData(), "END_POINT_NUM");
        }

        Long profileEndTime = MapUtil.getLong(ansiTaskData, "profileEndTime");
        // Determine whether to include time offset
        int i = 1;
        long loopIntervalTime = startIntervalTime;
        int loopPointNum = pointNum;
        int intervalSize = nbrChnsSet1 * 4;
        int pointSizePage = intervalSize >= MAX_READ_SIZE ? 1 : MAX_READ_SIZE / intervalSize;
        for (; i < pointSizePage; i++) {
            loopIntervalTime += interval;
            loopPointNum++;
            if(loopIntervalTime > profileEndTime) {
                break;
            }
            if(loopPointNum == nbrBlkIntsSet1) {
                break;
            }
        }
        atomicState.getData().put("CURRENT_INTERVAL_OFFSET", currentIntervalOffset + intervalSize * i);
        atomicState.getData().put("START_INTERVAL_TIME", startIntervalTime);
        atomicState.getData().put("INTERVAL_SIZE", i);
        atomicState.getData().put("END_POINT_NUM", pointNum + i);
        atomicState.getData().put("CURRENT_BLOCK_NUM", blockNum);
        redisUtils.hmset(ansiTaskDataKey, ansiTaskData, Long.valueOf(2 * 60 * 60));
        dataItemDTO.setTableOffset(currentIntervalOffset);
        dataItemDTO.setByteCount(nbrChnsSet1 * 4 * i);
        log.info("Reading LP Data Block Element {} Interval Block {} to {}", blockNum, pointNum + 1, pointNum + i);
        byte[] packets = packetService.buildReadOneAttributePacket(dataItemDTO, comConfigDMO, deviceKeys);
        log.debug(ByteUtils.byteToHexString(packets));
        connectorService.sendPacket(task, atomicTask, dataItemCosemList.get(0).getRoleId(), task.getIsODR(), packets);

        return atomicState;
    }

    @Override
    public Boolean handleFailure(Task task, AtomicTask atomicTask, String message) {
        log.info("error {}, ", message);
        if (task.getIsODR()) {
            Map<String, Object> param = getParameterMap(task, atomicTask);
            Long profileDataItemId = Long.valueOf(String.valueOf(param.get("dataItemId")));
            // key 回曲线的dataItemId给web
            taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), Convert.toStr(profileDataItemId), message, 3600l, false);
        }
        if ((TaskType.ScheduleRead_Profile.toString().equalsIgnoreCase(task.getTaskType()) && !message.equals(TaskExecuteErrorCode.SCHEDULE_TASK_IS_LATEST_PROCESS.toString()))
                || TaskType.EventRead_Profile.toString().equalsIgnoreCase(task.getTaskType())
                || TaskType.ScheduleRead_LogBook.toString().equalsIgnoreCase(task.getTaskType())) {
            logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Failed.getCode(), LogScheduleTaskDevice.StatusDescEnum.DecodeDataFailed.getCode());
        }
        return true;
    }
}
